<!DOCTYPE html>


<html lang="zh-Hans">


<head>
  <meta charset="utf-8" />
   
  <meta name="keywords" content="代码,生活,杂记,思考" />
   
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    深入浅出-Webpack学习笔记（三） |  AURORA
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">

  
<link rel="stylesheet" href="/css/custom.css">

  
  
<script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>

  
  

  

<link rel="alternate" href="/atom.xml" title="AURORA" type="application/atom+xml">
</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-深入浅出-Webpack学习笔记（三）"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  深入浅出-Webpack学习笔记（三）
</h1>
 

    </header>
     
    <div class="article-meta">
      <a href="/2020/08/03/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA-Webpack%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%89%EF%BC%89/" class="article-date">
  <time datetime="2020-08-03T01:58:21.628Z" itemprop="datePublished">2020-08-03 09:08:62</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/%E6%8A%80%E6%9C%AF/">技术</a> / <a class="article-category-link" href="/categories/%E6%8A%80%E6%9C%AF/%E5%89%8D%E7%AB%AF/">前端</a> / <a class="article-category-link" href="/categories/%E6%8A%80%E6%9C%AF/%E5%89%8D%E7%AB%AF/webpack/">webpack</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> Word count:</span>
            <span class="post-count">10.2k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> Reading time≈</span>
            <span class="post-count">41 min</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <p>接下来学习如何用 Webpack 去解决实际项目中常见的场景<br><a id="more"></a></p>
<h1 id="使用ES6"><a href="#使用ES6" class="headerlink" title="使用ES6"></a>使用ES6</h1><p>虽然目前部分浏览器和 Node.js 已经支持 ES6，但由于它们对 ES6 所有的标准支持不全，这导致在开发中不敢全面地使用 ES6。</p>
<p>通常我们需要把采用 ES6 编写的代码转换成目前已经支持良好的 ES5 代码，这包含2件事：</p>
<ul>
<li>把新的 ES6 语法用 ES5 实现，例如 ES6 的 class 语法用 ES5 的 prototype 实现。</li>
<li>给新的 API 注入 polyfill ，例如项目使用 fetch API 时，只有注入对应的 polyfill 后，才能在低版本浏览器中正常运行。</li>
</ul>
<h2 id="Babel"><a href="#Babel" class="headerlink" title="Babel"></a>Babel</h2><p>Babel 可以方便的完成以上2件事。 Babel 是一个 JavaScript 编译器，能将 ES6 代码转为 ES5 代码，让你使用最新的语言特性而不用担心兼容性问题，并且可以通过插件机制根据需求灵活的扩展。 在 Babel 执行编译的过程中，会从项目根目录下的 .babelrc 文件读取配置。.babelrc 是一个 JSON 格式的文件，内容大致如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">"plugins"</span>: [</span><br><span class="line">    [</span><br><span class="line">      <span class="string">"transform-runtime"</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="string">"polyfill"</span>: <span class="literal">false</span></span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">   ],</span><br><span class="line">  <span class="string">"presets"</span>: [</span><br><span class="line">    [</span><br><span class="line">      <span class="string">"es2015"</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="string">"modules"</span>: <span class="literal">false</span></span><br><span class="line">      &#125;</span><br><span class="line">    ],</span><br><span class="line">    <span class="string">"stage-2"</span>,</span><br><span class="line">    <span class="string">"react"</span></span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="Plugins"><a href="#Plugins" class="headerlink" title="Plugins"></a>Plugins</h2><p>plugins 属性告诉 Babel 要使用哪些插件，插件可以控制如何转换代码。</p>
<p>以上配置文件里的 transform-runtime 对应的插件全名叫做 babel-plugin-transform-runtime，即在前面加上了 babel-plugin-，要让 Babel 正常运行我们必须先安装它：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -D babel-plugin-transform-runtime</span><br></pre></td></tr></table></figure>
<p>babel-plugin-transform-runtime 是 Babel 官方提供的一个插件，作用是减少冗余代码。 Babel 在把 ES6 代码转换成 ES5 代码时通常需要一些 ES5 写的辅助函数来完成新语法的实现，例如在转换 class extent 语法时会在转换后的 ES5 代码里注入 _extent 辅助函数用于实现继承：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">_extent</span>(<span class="params">target</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">1</span>; i &lt; <span class="built_in">arguments</span>.length; i++) &#123;</span><br><span class="line">    <span class="keyword">var</span> source = <span class="built_in">arguments</span>[i];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> source) &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="built_in">Object</span>.prototype.hasOwnProperty.call(source, key)) &#123;</span><br><span class="line">        target[key] = source[key];</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> target;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>这会导致每个使用了 class extent 语法的文件都被注入重复的_extent 辅助函数代码，babel-plugin-transform-runtime 的作用在于不把辅助函数内容注入到文件里，而是注入一条导入语句：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> _extent = <span class="built_in">require</span>(<span class="string">'babel-runtime/helpers/_extent'</span>);</span><br></pre></td></tr></table></figure>
<p>这样能减小 Babel 编译出来的代码的文件大小。</p>
<p>同时需要注意的是由于 babel-plugin-transform-runtime 注入了 require(‘babel-runtime/helpers/_extent’) 语句到编译后的代码里，需要安装 babel-runtime 依赖到你的项目后，代码才能正常运行。 也就是说 babel-plugin-transform-runtime 和 babel-runtime 需要配套使用，使用了 babel-plugin-transform-runtime 后一定需要 babel-runtime。</p>
<h2 id="Presets"><a href="#Presets" class="headerlink" title="Presets"></a>Presets</h2><p>presets 属性告诉 Babel 要转换的源码使用了哪些新的语法特性，一个 Presets 对一组新语法特性提供支持，多个 Presets 可以叠加。 Presets 其实是一组 Plugins 的集合，每一个 Plugin 完成一个新语法的转换工作。Presets 是按照 ECMAScript 草案来组织的，通常可以分为以下三大类：</p>
<p>已经被写入 ECMAScript 标准里的特性，由于之前每年都有新特性被加入到标准里，所以又可细分为：</p>
<ul>
<li>es2015 包含在2015里加入的新特性；</li>
<li>es2016 包含在2016里加入的新特性；</li>
<li>es2017 包含在2017里加入的新特性；</li>
</ul>
<p>env 包含当前所有 ECMAScript 标准里的最新特性。</p>
<p>它们之间的关系如图：</p>
<p><img src="https://img-blog.csdnimg.cn/20200730144706933.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTIwMzE5NTg=,size_16,color_FFFFFF,t_70" alt=""><br>被社区提出来的但还未被写入 ECMAScript 标准里特性，这其中又分为以下四种：</p>
<ul>
<li>stage0 只是一个美好激进的想法，有 Babel 插件实现了对这些特性的支持，但是不确定是否会被定为标准；</li>
<li>stage1 值得被纳入标准的特性；</li>
<li>stage2 该特性规范已经被起草，将会被纳入标准里；</li>
<li>stage3 该特性规范已经定稿，各大浏览器厂商和 Node.js 社区开始着手实现；</li>
<li>stage4 在接下来的一年将会加入到标准里去。<br>它们之间的关系如图：</li>
</ul>
<p><img src="https://img-blog.csdnimg.cn/2020073014493659.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTIwMzE5NTg=,size_16,color_FFFFFF,t_70" alt=""></p>
<p>为了支持一些特定应用场景下的语法，和 ECMAScript 标准没有关系，例如 babel-preset-react 是为了支持 React 开发中的 JSX 语法。<br>在实际应用中，你需要根据项目源码所使用的语法去安装对应的 Plugins 或 Presets。</p>
<h2 id="接入-Babel"><a href="#接入-Babel" class="headerlink" title="接入 Babel"></a>接入 Babel</h2><p>在了解 Babel 后，下一步要知道如何在 Webpack 中使用它。 由于 Babel 所做的事情是转换代码，所以应该通过 Loader 去接入 Babel，Webpack 配置如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.js$/</span>,</span><br><span class="line">        use: [<span class="string">'babel-loader'</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// 输出 source-map 方便直接调试 ES6 源码</span></span><br><span class="line">  devtool: <span class="string">'source-map'</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>配置命中了项目目录下所有的 JavaScript 文件，通过 babel-loader 去调用 Babel 完成转换工作。 在重新执行构建前，需要先安装新引入的依赖：</p>
<p>Webpack 接入 Babel 必须依赖的模块</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -D babel-core babel-loader</span><br></pre></td></tr></table></figure>
<p>根据你的需求选择不同的 Plugins 或 Presets</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -D babel-preset-env</span><br></pre></td></tr></table></figure>
<h1 id="使用TS"><a href="#使用TS" class="headerlink" title="使用TS"></a>使用TS</h1><p>要让 Webpack 支持 TypeScript，需要解决以下2个问题：</p>
<ul>
<li>通过 Loader 把 TypeScript 转换成 JavaScript。</li>
<li>Webpack 在寻找模块对应的文件时需要尝试 ts 后缀。</li>
</ul>
<p>对于问题1，社区已经出现了几个可用的 Loader，推荐速度更快的 awesome-typescript-loader。 对于问题2，根据2-4 Resolve 中的 extensions 我们需要修改默认的 resolve.extensions 配置项。</p>
<p>综上，相关 Webpack 配置如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="comment">// 执行入口文件</span></span><br><span class="line">  entry: <span class="string">'./main'</span>,</span><br><span class="line">  output: &#123;</span><br><span class="line">    filename: <span class="string">'bundle.js'</span>,</span><br><span class="line">    path: path.resolve(__dirname, <span class="string">'./dist'</span>),</span><br><span class="line">  &#125;,</span><br><span class="line">  resolve: &#123;</span><br><span class="line">    <span class="comment">// 先尝试 ts 后缀的 TypeScript 源码文件</span></span><br><span class="line">    extensions: [<span class="string">'.ts'</span>, <span class="string">'.js'</span>]</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.ts$/</span>,</span><br><span class="line">        loader: <span class="string">'awesome-typescript-loader'</span></span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">  devtool: <span class="string">'source-map'</span>,<span class="comment">// 输出 Source Map 方便在浏览器里调试 TypeScript 代码</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>在运行构建前需要安装上面用到的依赖：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -D typescript awesome-typescript-loader</span><br></pre></td></tr></table></figure>
<p>安装成功后重新执行构建，你将会在 dist 目录看到输出的 JavaScript 文件 bundle.js，和对应的 Source Map 文件 bundle.js.map。 在浏览器里打开 index.html 页面后，来开发工具里可以看到和调试用 TypeScript 编写的源码。</p>
<blockquote>
<p>注意：截止2020年7月，webpack版本为4.44版本，解析ts可以使用ts-loader的4.X版本</p>
</blockquote>
<p>你需要在当前项目根目录下新建一个用于配置编译选项的 tsconfig.json 文件，编译器默认会读取和使用这个文件，配置文件内容大致如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">"compilerOptions"</span>: &#123;</span><br><span class="line">    <span class="string">"module"</span>: <span class="string">"commonjs"</span>, <span class="comment">// 编译出的代码采用的模块规范</span></span><br><span class="line">    <span class="string">"target"</span>: <span class="string">"es5"</span>, <span class="comment">// 编译出的代码采用 ES 的哪个版本</span></span><br><span class="line">    <span class="string">"sourceMap"</span>: <span class="literal">true</span> <span class="comment">// 输出 Source Map 方便调试</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="string">"exclude"</span>: [ <span class="comment">// 不编译这些目录里的文件</span></span><br><span class="line">    <span class="string">"node_modules"</span></span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h1 id="使用React"><a href="#使用React" class="headerlink" title="使用React"></a>使用React</h1><p>使用了 React 项目的代码特征有 JSX 和 Class 语法，例如：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Button</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  render() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello,Webpack<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在使用了 React 的项目里 JSX 和 Class 语法并不是必须的，但使用新语法写出的代码看上去更优雅。</p>
<p>其中 JSX 语法是无法在任何现有的 JavaScript 引擎中运行的，所以在构建过程中需要把源码转换成可以运行的代码，例如：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 原 JSX 语法代码</span></span><br><span class="line"><span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello,Webpack<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 被转换成正常的 JavaScript 代码</span></span><br><span class="line"><span class="keyword">return</span> React.createElement(<span class="string">'h1'</span>, <span class="literal">null</span>, <span class="string">'Hello,Webpack'</span>)</span><br></pre></td></tr></table></figure>
<p>目前 Babel 和 TypeScript 都提供了对 React 语法的支持，下面分别来介绍如何在使用 Babel 或 TypeScript 的项目中接入 React 框架。</p>
<h2 id="React-与-Babel"><a href="#React-与-Babel" class="headerlink" title="React 与 Babel"></a>React 与 Babel</h2><p>要在使用 Babel 的项目中接入 React 框架是很简单的，只需要加入 React 所依赖的 Presets babel-preset-react。 接下来通过修改前面讲过的3-1 使用 ES6 语言中的项目，为其接入 React 框架。</p>
<p>通过以下命令：</p>
<p><strong>安装 React 基础依赖</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -D react react-dom</span><br></pre></td></tr></table></figure>
<p><strong>安装 babel 完成语法转换所需依赖</strong></p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -D babel-preset-react</span><br></pre></td></tr></table></figure>
<p>安装新的依赖后，再修改 .babelrc 配置文件加入 React Presets</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">"presets"</span>: [</span><br><span class="line">    <span class="string">"react"</span></span><br><span class="line">],</span><br></pre></td></tr></table></figure>
<p>就完成了一切准备工作。</p>
<p>再修改 main.js 文件如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> React <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; Component &#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; render &#125; <span class="keyword">from</span> <span class="string">'react-dom'</span>;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Button</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  render() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello,Webpack<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">render(<span class="xml"><span class="tag">&lt;<span class="name">Button</span>/&gt;</span></span>, <span class="built_in">window</span>.document.getElementById(<span class="string">'app'</span>));</span><br></pre></td></tr></table></figure>
<p>重新执行构建打开网页你将会发现由 React 渲染出来的 Hello,Webpack。</p>
<h2 id="React-与-TypeScript"><a href="#React-与-TypeScript" class="headerlink" title="React 与 TypeScript"></a>React 与 TypeScript</h2><p>TypeScript 相比于 Babel 的优点在于它原生支持 JSX 语法，你不需要重新安装新的依赖，只需修改一行配置。 但 TypeScript 的不同在于：</p>
<ul>
<li>使用了 JSX 语法的文件后缀必须是 tsx。</li>
<li>由于 React 不是采用 TypeScript 编写的，需要安装 react 和 react-dom 对应的 TypeScript 接口描述模块 @types/react 和 @types/react-dom 后才能通过编译。</li>
</ul>
<p>接下来通过修改3-2 使用 TypeScript 语言中讲过的的项目，为其接入 React 框架。 修改 TypeScript 编译器配置文件 tsconfig.json 增加对 JSX 语法的支持，如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">"compilerOptions"</span>: &#123;</span><br><span class="line">    <span class="string">"jsx"</span>: <span class="string">"react"</span> <span class="comment">// 开启 jsx ，支持 React</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>由于 main.js 文件中存在 JSX 语法，再把 main.js 文件重命名为 main.tsx，同时修改文件内容为在上面 React 与 Babel 里所采用的 React 代码。 同时为了让 Webpack 对项目里的 ts 与 tsx 原文件都采用 awesome-typescript-loader 去转换， 需要注意的是 Webpack Loader 配置的 test 选项需要匹配到 tsx 类型的文件，并且 extensions 中也要加上 .tsx，配置如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="comment">// TS 执行入口文件</span></span><br><span class="line">  entry: <span class="string">'./main'</span>,</span><br><span class="line">  output: &#123;</span><br><span class="line">    filename: <span class="string">'bundle.js'</span>,</span><br><span class="line">    path: path.resolve(__dirname, <span class="string">'./dist'</span>),</span><br><span class="line">  &#125;,</span><br><span class="line">  resolve: &#123;</span><br><span class="line">    <span class="comment">// 先尝试 ts，tsx 后缀的 TypeScript 源码文件</span></span><br><span class="line">    extensions: [<span class="string">'.ts'</span>, <span class="string">'.tsx'</span>, <span class="string">'.js'</span>,]</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="comment">// 同时匹配 ts，tsx 后缀的 TypeScript 源码文件</span></span><br><span class="line">        test: <span class="regexp">/\.tsx?$/</span>,</span><br><span class="line">        loader: <span class="string">'awesome-typescript-loader'</span></span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">  devtool: <span class="string">'source-map'</span>,<span class="comment">// 输出 Source Map 方便在浏览器里调试 TypeScript 代码</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>通过</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i react react-dom @types/react @types/react-dom</span><br></pre></td></tr></table></figure>
<p>安装新的依赖后重启构建，重新打开网页你将会发现由 React 渲染出来的 Hello,Webpack</p>
<h1 id="使用Vue"><a href="#使用Vue" class="headerlink" title="使用Vue"></a>使用Vue</h1><h2 id="认识-Vue"><a href="#认识-Vue" class="headerlink" title="认识 Vue"></a>认识 Vue</h2><p>Vue 和 React 一样，它们都推崇组件化和由数据驱动视图的思想，视图和数据绑定在一起，数据改变视图会跟着改变，而无需直接操作视图。 还是以前面的 Hello,Webpack 为例，来看下 Vue 版本的实现。</p>
<p>App.vue 文件代表一个单文件组件，它是项目唯一的组件，也是根组件：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">&lt;!--渲染模版--&gt;</span><br><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;h1&gt;&#123;&#123; msg &#125;&#125;&lt;<span class="regexp">/h1&gt;</span></span><br><span class="line"><span class="regexp">&lt;/</span>template&gt;</span><br><span class="line"></span><br><span class="line">&lt;!--样式描述--&gt;</span><br><span class="line">&lt;style scoped&gt;</span><br><span class="line">  h1 &#123;</span><br><span class="line">    color: red;</span><br><span class="line">  &#125;</span><br><span class="line">&lt;<span class="regexp">/style&gt;</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">&lt;!--组件逻辑--&gt;</span></span><br><span class="line"><span class="regexp">&lt;script&gt;</span></span><br><span class="line"><span class="regexp">  export default &#123;</span></span><br><span class="line"><span class="regexp">    data() &#123;</span></span><br><span class="line"><span class="regexp">      return &#123;</span></span><br><span class="line"><span class="regexp">        msg: 'Hello,Webpack'</span></span><br><span class="line"><span class="regexp">      &#125;</span></span><br><span class="line"><span class="regexp">    &#125;</span></span><br><span class="line"><span class="regexp">  &#125;</span></span><br><span class="line"><span class="regexp">&lt;/</span>script&gt;</span><br></pre></td></tr></table></figure>
<p>Vue 的单文件组件通过一个类似 HTML 文件的 .vue 文件就能描述清楚一个组件所需的模版、样式、逻辑。</p>
<p>main.js 入口文件：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> App <span class="keyword">from</span> <span class="string">'./App.vue'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> Vue(&#123;</span><br><span class="line">  el: <span class="string">'#app'</span>,</span><br><span class="line">  render: <span class="function"><span class="params">h</span> =&gt;</span> h(App)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>入口文件创建一个 Vue 的根实例，在 ID 为 app 的 DOM 节点上渲染出上面定义的 App 组件。</p>
<h2 id="接入-Webpack"><a href="#接入-Webpack" class="headerlink" title="接入 Webpack"></a>接入 Webpack</h2><p>目前最成熟和流行的开发 Vue 项目的方式是采用 ES6 加 Babel 转换，这和基本的采用 ES6 开发的项目很相似，差别在于要解析 .vue 格式的单文件组件。 好在 Vue 官方提供了对应的 vue-loader 可以非常方便的完成单文件组件的转换。</p>
<p>修改 Webpack 相关配置如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>: &#123;</span><br><span class="line">  rules: [</span><br><span class="line">    &#123;</span><br><span class="line">      test: <span class="regexp">/\.vue$/</span>,</span><br><span class="line">      use: [<span class="string">'vue-loader'</span>],</span><br><span class="line">    &#125;,</span><br><span class="line">  ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>安装新引入的依赖：</p>
<p>Vue 框架运行需要的库</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -S vue</span><br></pre></td></tr></table></figure>
<p>构建所需的依赖</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -D vue-loader css-loader vue-template-compiler</span><br></pre></td></tr></table></figure>
<p>在这些依赖中，它们的作用分别是：</p>
<ul>
<li>vue-loader：解析和转换 .vue 文件，提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template，再分别把它们交给对应的 Loader 去处理。</li>
<li>css-loader：加载由 vue-loader 提取出的 CSS 代码。</li>
<li>vue-template-compiler：把 vue-loader 提取出的 HTML 模版编译成对应的可执行的 JavaScript 代码，这和 React 中的 JSX 语法被编译成 JavaScript 代码类似。预先编译好 HTML 模版相对于在浏览器中再去编译 HTML 模版的好处在于性能更好。</li>
</ul>
<p>重新启动构建你就能看到由 Vue 渲染出的 Hello,Webpack 了。</p>
<h2 id="使用-TypeScript-编写-Vue-应用"><a href="#使用-TypeScript-编写-Vue-应用" class="headerlink" title="使用 TypeScript 编写 Vue 应用"></a>使用 TypeScript 编写 Vue 应用</h2><p>从 Vue 2.5.0+ 版本开始，提供了对 TypeScript 的良好支持，使用 TypeScript 编写 Vue 是一个很好的选择，因为 TypeScript 能检查出一些潜在的错误。 下面讲解如何用 Webpack 构建使用 TypeScript 编写的 Vue 应用。</p>
<p>新增 tsconfig.json 配置文件，内容如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">"compilerOptions"</span>: &#123;</span><br><span class="line">    <span class="comment">// 构建出 ES5 版本的 JavaScript，与 Vue 的浏览器支持保持一致</span></span><br><span class="line">    <span class="string">"target"</span>: <span class="string">"es5"</span>,</span><br><span class="line">    <span class="comment">// 开启严格模式，这可以对 `this` 上的数据属性进行更严格的推断</span></span><br><span class="line">    <span class="string">"strict"</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="comment">// TypeScript 编译器输出的 JavaScript 采用 es2015 模块化，使 Tree Shaking 生效</span></span><br><span class="line">    <span class="string">"module"</span>: <span class="string">"es2015"</span>,</span><br><span class="line">    <span class="string">"moduleResolution"</span>: <span class="string">"node"</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>以上代码中的 “module”: “es2015” 是为了 Tree Shaking 优化生效，阅读 4-10 使用 TreeShaking 进一步了解。</p>
<p>修改 App.vue 脚本部分内容如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">&lt;!--组件逻辑--&gt;</span><br><span class="line">&lt;script lang=<span class="string">"ts"</span>&gt;</span><br><span class="line">  <span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">"vue"</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 通过 Vue.extend 启用 TypeScript 类型推断</span></span><br><span class="line">  <span class="keyword">export</span> <span class="keyword">default</span> Vue.extend(&#123;</span><br><span class="line">    data() &#123;</span><br><span class="line">      <span class="keyword">return</span> &#123;</span><br><span class="line">        msg: <span class="string">'Hello,Webpack'</span>,</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;);</span><br><span class="line">&lt;<span class="regexp">/script&gt;</span></span><br></pre></td></tr></table></figure>
<p>注意 script 标签中的 lang=”ts” 是为了指明代码的语法是 TypeScript。</p>
<p>修改 main.ts 执行入口文件为如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">'vue'</span></span><br><span class="line"><span class="keyword">import</span> App <span class="keyword">from</span> <span class="string">'./App.vue'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> Vue(&#123;</span><br><span class="line">  el: <span class="string">'#app'</span>,</span><br><span class="line">  render: <span class="function"><span class="params">h</span> =&gt;</span> h(App)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>由于 TypeScript 不认识 .vue 结尾的文件，为了让其支持 import App from ‘./App.vue’ 导入语句，还需要以下文件 vue-shims.d.ts 去定义 .vue 的类型：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 告诉 TypeScript 编译器 .vue 文件其实是一个 Vue</span></span><br><span class="line">declare <span class="built_in">module</span> <span class="string">"*.vue"</span> &#123;</span><br><span class="line">  <span class="keyword">import</span> Vue <span class="keyword">from</span> <span class="string">"vue"</span>;</span><br><span class="line">  <span class="keyword">export</span> <span class="keyword">default</span> Vue;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>Webpack 配置需要修改两个地方，如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  resolve: &#123;</span><br><span class="line">    <span class="comment">// 增加对 TypeScript 的 .ts 和 .vue 文件的支持</span></span><br><span class="line">    extensions: [<span class="string">'.ts'</span>, <span class="string">'.js'</span>, <span class="string">'.vue'</span>, <span class="string">'.json'</span>],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      <span class="comment">// 加载 .ts 文件</span></span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.ts$/</span>,</span><br><span class="line">        loader: <span class="string">'ts-loader'</span>,</span><br><span class="line">        exclude: <span class="regexp">/node_modules/</span>,</span><br><span class="line">        options: &#123;</span><br><span class="line">          <span class="comment">// 让 tsc 把 vue 文件当成一个 TypeScript 模块去处理，以解决 moudle not found 的问题，tsc 本身不会处理 .vue 结尾的文件</span></span><br><span class="line">          appendTsSuffixTo: [<span class="regexp">/\.vue$/</span>],</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;,</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>除此之外还需要安装新引入的依赖：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -D ts-loader typescript</span><br></pre></td></tr></table></figure>
<h1 id="构建同构应用"><a href="#构建同构应用" class="headerlink" title="构建同构应用"></a>构建同构应用</h1><h2 id="认识同构应用"><a href="#认识同构应用" class="headerlink" title="认识同构应用"></a>认识同构应用</h2><p>现在大多数单页应用的视图都是通过 JavaScript 代码在<strong>浏览器端</strong>渲染出来的，但在浏览器端渲染的坏处有：</p>
<ul>
<li>搜索引擎无法收录你的网页，因为展示出的数据都是在浏览器端异步渲染出来的，大部分爬虫无法获取到这些数据。</li>
<li>对于复杂的单页应用，渲染过程计算量大，对低端移动设备来说可能会有性能问题，用户能明显感知到首屏的渲染延迟。</li>
</ul>
<p>为了解决以上问题，有人提出能否将原本只运行在浏览器中的 JavaScript 渲染代码也在服务器端运行，在服务器端渲染出带内容的 HTML 后再返回。 这样就能让搜索引擎爬虫直接抓取到带数据的 HTML，同时也能降低首屏渲染时间。 由于 Node.js 的流行和成熟，以及虚拟 DOM 提出与实现，使这个假设成为可能。</p>
<p>实际上现在主流的前端框架都支持同构，包括 React、Vue2、Angular2，其中最先支持也是最成熟的同构方案是 React。 由于 React 使用者更多，它们之间又很相似，本节只介绍如何用 Webpack 构建 React 同构应用。</p>
<p>同构应用运行原理的核心在于虚拟 DOM，虚拟 DOM 的意思是不直接操作 DOM 而是通过 JavaScript Object 去描述原本的 DOM 结构。 在需要更新 DOM 时不直接操作 DOM 树，而是通过更新 JavaScript Object 后再映射成 DOM 操作。</p>
<p>虚拟 DOM 的优点在于：</p>
<ul>
<li>因为操作 DOM 树是高耗时的操作，尽量减少 DOM 树操作能优化网页性能。而 DOM Diff 算法能找出2个不同 Object 的最小差异，得出最小 DOM 操作；</li>
<li>虚拟 DOM 的在渲染的时候不仅仅可以通过操作 DOM 树来表示出结果，也能有其它的表示方式，例如把虚拟 DOM 渲染成字符串(服务器端渲染)，或者渲染成手机 App 原生的 UI 组件( React Native)。</li>
</ul>
<p>以 React 为例，核心模块 react 负责管理 React 组件的生命周期，而具体的渲染工作可以交给 react-dom 模块来负责。</p>
<p>react-dom 在渲染虚拟 DOM 树时有2中方式可选：</p>
<ul>
<li>通过 render() 函数去操作浏览器 DOM 树来展示出结果。</li>
<li>通过 renderToString() 计算出表示虚拟 DOM 的 HTML 形式的字符串。</li>
</ul>
<p>构建同构应用的最终目的是从一份项目源码中构建出2份 JavaScript 代码，一份用于在浏览器端运行，一份用于在 Node.js 环境中运行渲染出 HTML。 其中用于在 Node.js 环境中运行的 JavaScript 代码需要注意以下几点：</p>
<ul>
<li>不能包含浏览器环境提供的 API，例如使用 document 进行 DOM 操作， 　因为 Node.js 不支持这些 API；</li>
<li>不能包含 CSS 代码，因为服务端渲染的目的是渲染出 HTML 内容，渲染出 CSS 代码会增加额外的计算量，影响服务端渲染性能；</li>
<li>不能像用于浏览器环境的输出代码那样把 node_modules 里的第三方模块和 Node.js 原生模块(例如 fs 模块)打包进去，而是需要通过 CommonJS 规范去引入这些模块。</li>
<li>需要通过 CommonJS 规范导出一个渲染函数，以用于在 HTTP 服务器中去执行这个渲染函数，渲染出 HTML 内容返回。</li>
</ul>
<h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>接下来改造在3-6使用 React 框架中介绍的 React 项目，为它增加构建同构应用的功能。</p>
<p>由于要从一份源码构建出2份不同的代码，需要有2份 Webpack 配置文件分别与之对应。 构建用于浏览器环境的配置和前面讲的没有差别，本节侧重于讲如何构建用于服务端渲染的代码。</p>
<p>用于构建浏览器环境代码的 webpack.config.js 配置文件保留不变，新建一个专门用于构建服务端渲染代码的配置文件 webpack_server.config.js，内容如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"><span class="keyword">const</span> nodeExternals = <span class="built_in">require</span>(<span class="string">'webpack-node-externals'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="comment">// JS 执行入口文件</span></span><br><span class="line">  entry: <span class="string">'./main_server.js'</span>,</span><br><span class="line">  <span class="comment">// 为了不把 Node.js 内置的模块打包进输出文件中，例如 fs net 模块等</span></span><br><span class="line">  target: <span class="string">'node'</span>,</span><br><span class="line">  <span class="comment">// 为了不把 node_modules 目录下的第三方模块打包进输出文件中</span></span><br><span class="line">  externals: [nodeExternals()],</span><br><span class="line">  output: &#123;</span><br><span class="line">    <span class="comment">// 为了以 CommonJS2 规范导出渲染函数，以给采用 Node.js 编写的 HTTP 服务调用</span></span><br><span class="line">    libraryTarget: <span class="string">'commonjs2'</span>,</span><br><span class="line">    <span class="comment">// 把最终可在 Node.js 中运行的代码输出到一个 bundle_server.js 文件</span></span><br><span class="line">    filename: <span class="string">'bundle_server.js'</span>,</span><br><span class="line">    <span class="comment">// 输出文件都放到 dist 目录下</span></span><br><span class="line">    path: path.resolve(__dirname, <span class="string">'./dist'</span>),</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.js$/</span>,</span><br><span class="line">        use: [<span class="string">'babel-loader'</span>],</span><br><span class="line">        exclude: path.resolve(__dirname, <span class="string">'node_modules'</span>),</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="comment">// CSS 代码不能被打包进用于服务端的代码中去，忽略掉 CSS 文件</span></span><br><span class="line">        test: <span class="regexp">/\.css$/</span>,</span><br><span class="line">        use: [<span class="string">'ignore-loader'</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">  devtool: <span class="string">'source-map'</span> <span class="comment">// 输出 source-map 方便直接调试 ES6 源码</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>以上代码有几个关键的地方，分别是：</p>
<ul>
<li><code>target: &#39;node&#39;</code> 由于输出代码的运行环境是 Node.js，源码中依赖的 Node.js 原生模块没必要打包进去；</li>
<li><code>externals: [nodeExternals()] webpack-node-externals</code> 的目的是为了防止 node_modules 目录下的第三方模块被打包进去，因为 Node.js 默认会去 node_modules 目录下寻找和使用第三方模块；</li>
<li><code>{test: /\.css$/, use: [&#39;ignore-loader&#39;]}</code> 忽略掉依赖的 CSS 文件，CSS 会影响服务端渲染性能，又是做服务端渲不重要的部分；</li>
<li><code>libraryTarget: &#39;commonjs2&#39;</code> 以 CommonJS2 规范导出渲染函数，以供给采用 Node.js 编写的 HTTP 服务器代码调用。</li>
</ul>
<p>为了最大限度的复用代码，需要调整下目录结构：</p>
<p>把页面的根组件放到一个单独的文件 AppComponent.js，该文件只能包含根组件的代码，不能包含渲染入口的代码，而且需要导出根组件以供给渲染入口调用，AppComponent.js 内容如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React, &#123; Component &#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">'./main.css'</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="class"><span class="keyword">class</span> <span class="title">AppComponent</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">  render() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello,Webpack<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>分别为不同环境的渲染入口写两份不同的文件，分别是用于浏览器端渲染 DOM 的 main_browser.js 文件，和用于服务端渲染 HTML 字符串的 main_server.js 文件。</p>
<p>main_browser.js 文件内容如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; render &#125; <span class="keyword">from</span> <span class="string">'react-dom'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; AppComponent &#125; <span class="keyword">from</span> <span class="string">'./AppComponent'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 把根组件渲染到 DOM 树上</span></span><br><span class="line">render(<span class="xml"><span class="tag">&lt;<span class="name">AppComponent</span>/&gt;</span></span>, <span class="built_in">window</span>.document.getElementById(<span class="string">'app'</span>));</span><br></pre></td></tr></table></figure>
<p>main_server.js 文件内容如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; renderToString &#125; <span class="keyword">from</span> <span class="string">'react-dom/server'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; AppComponent &#125; <span class="keyword">from</span> <span class="string">'./AppComponent'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 导出渲染函数，以给采用 Node.js 编写的 HTTP 服务器代码调用</span></span><br><span class="line"><span class="keyword">export</span> <span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="comment">// 把根组件渲染成 HTML 字符串</span></span><br><span class="line">  <span class="keyword">return</span> renderToString(<span class="xml"><span class="tag">&lt;<span class="name">AppComponent</span>/&gt;</span></span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>为了能把渲染的完整 HTML 文件通过 HTTP 服务返回给请求端，还需要通过用 Node.js 编写一个 HTTP 服务器。 由于本节不专注于将 HTTP 服务器的实现，就采用了 ExpressJS 来实现，http_server.js 文件内容如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">'express'</span>);</span><br><span class="line"><span class="keyword">const</span> &#123; render &#125; = <span class="built_in">require</span>(<span class="string">'./dist/bundle_server'</span>);</span><br><span class="line"><span class="keyword">const</span> app = express();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 调用构建出的 bundle_server.js 中暴露出的渲染函数，再拼接下 HTML 模版，形成完整的 HTML 文件</span></span><br><span class="line">app.get(<span class="string">'/'</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">  res.send(<span class="string">`</span></span><br><span class="line"><span class="string">&lt;html&gt;</span></span><br><span class="line"><span class="string">&lt;head&gt;</span></span><br><span class="line"><span class="string">  &lt;meta charset="UTF-8"&gt;</span></span><br><span class="line"><span class="string">&lt;/head&gt;</span></span><br><span class="line"><span class="string">&lt;body&gt;</span></span><br><span class="line"><span class="string">&lt;div id="app"&gt;<span class="subst">$&#123;render()&#125;</span>&lt;/div&gt;</span></span><br><span class="line"><span class="string">&lt;!--导入 Webpack 输出的用于浏览器端渲染的 JS 文件--&gt;</span></span><br><span class="line"><span class="string">&lt;script src="./dist/bundle_browser.js"&gt;&lt;/script&gt;</span></span><br><span class="line"><span class="string">&lt;/body&gt;</span></span><br><span class="line"><span class="string">&lt;/html&gt;</span></span><br><span class="line"><span class="string">  `</span>);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 其它请求路径返回对应的本地文件</span></span><br><span class="line">app.use(express.static(<span class="string">'.'</span>));</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">3000</span>, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'app listening on port 3000!'</span>)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>再安装新引入的第三方依赖：</p>
<p>安装 Webpack 构建依赖</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -D css-loader style-loader ignore-loader webpack-node-externals</span><br></pre></td></tr></table></figure>
<p>安装 HTTP 服务器依赖</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -S express</span><br></pre></td></tr></table></figure>
<p>以上所有准备工作已经完成，接下来执行构建，编译出目标文件：</p>
<p>执行命令 <code>webpack --config webpack_server.config.js</code> 构建出用于服务端渲染的 ./dist/bundle_server.js 文件。<br>执行命令 webpack 构建出用于浏览器环境运行的 ./dist/bundle_browser.js 文件，默认的配置文件为 webpack.config.js。<br>构建执行完成后，执行 node ./http_server.js 启动 HTTP 服务器后，再用浏览器去访问 <a href="http://localhost:3000" target="_blank" rel="noopener">http://localhost:3000</a> 就能看到 Hello,Webpack 了。 但是为了验证服务端渲染的结果，你需要打开浏览器的开发工具中的网络抓包一栏，再重新刷新浏览器后，就能抓到请求 HTML 的包了，抓包效果图如下：</p>
<p><img src="https://img-blog.csdnimg.cn/20200730174656758.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTIwMzE5NTg=,size_16,color_FFFFFF,t_70" alt=""></p>
<p>可以看到服务器返回的是渲染出内容后的 HTML 而不是 HTML 模版，这说明同构应用的改造完成。</p>
<h1 id="检查代码"><a href="#检查代码" class="headerlink" title="检查代码"></a>检查代码</h1><p>当项目代码变得日益庞大复杂时，如何保障代码质量？如何保障多人协助开发时代码的可读性？</p>
<p>完全解决以上问题不是一个简单的事，但做检查代码能解决大部分问题。本节将教你如何结合构建做代码检查。</p>
<h2 id="代码检查具体是做什么"><a href="#代码检查具体是做什么" class="headerlink" title="代码检查具体是做什么"></a>代码检查具体是做什么</h2><p>检查代码和 Code Review 很相似，都是去审视提交的代码可能存在的问题。 但 Code Review 一般通过人去执行，而检查代码是通过机器去执行一些自动化的检查。 自动化的检查代码成本更低，实施代价更小。</p>
<p>检查代码主要检查以下几项：</p>
<ul>
<li>代码风格：让项目成员强制遵守统一的代码风格，例如如何缩进、如何写注释等，保障代码可读性，不把时间浪费在争论如何写代码更好看上；</li>
<li>潜在问题：分析出代码在运行过程中可能出现的潜在 Bug。</li>
</ul>
<p>其中检查代码风格相关的工具很多也很成熟，分析潜在问题的检查由于情况复杂目前还没有成熟的工具。</p>
<p>目前已经有成熟的工具可以检验诸如 JavaScript、TypeScript、CSS、SCSS 等常用语言。</p>
<h2 id="怎么做代码检查"><a href="#怎么做代码检查" class="headerlink" title="怎么做代码检查"></a>怎么做代码检查</h2><p>在做代码风格检查时需要按照不同的文件类型来检查，下面来分别介绍。</p>
<h3 id="检查-JavaScript"><a href="#检查-JavaScript" class="headerlink" title="检查 JavaScript"></a>检查 JavaScript</h3><p>目前最常用的 JavaScript 检查工具是 ESlint ，它不仅内置了大量常用的检查规则，还可以通过插件机制做到灵活扩展。</p>
<p>ESlint 的使用很简单，在通过</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -g eslint</span><br></pre></td></tr></table></figure>
<p>按照到全局后，再在项目目录下执行</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eslint --init</span><br></pre></td></tr></table></figure>
<p>来新建一个 ESlint 配置文件 .eslintrc，该文件格式为 JSON。</p>
<p>如果你想覆盖默认的检查规则，或者想加入新的检查规则，你需要修改该文件，例如使用以下配置：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="comment">// 从 eslint:recommended 中继承所有检查规则</span></span><br><span class="line">    <span class="string">"extends"</span>: <span class="string">"eslint:recommended"</span>,</span><br><span class="line">    <span class="comment">// 再自定义一些规则     </span></span><br><span class="line">    <span class="string">"rules"</span>: &#123;</span><br><span class="line">        <span class="comment">// 需要在每行结尾加 ;        </span></span><br><span class="line">        <span class="string">"semi"</span>: [<span class="string">"error"</span>, <span class="string">"always"</span>],</span><br><span class="line">        <span class="comment">// 需要使用 "" 包裹字符串         </span></span><br><span class="line">        <span class="string">"quotes"</span>: [<span class="string">"error"</span>, <span class="string">"double"</span>]</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>写好配置文件后，再执行</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">eslint yourfile.js</span><br></pre></td></tr></table></figure>
<p>去检查 yourfile.js 文件，如果你的文件没有通过检查，ESlint 会输出错误原因，例如：</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">/yourfile.js</span><br><span class="line">   296:13  error  Strings must use doublequote  quotes</span><br><span class="line">   298:7   error  Missing semicolon             semi</span><br><span class="line"></span><br><span class="line">✖ 2 problems (2 errors, 0 warnings)</span><br></pre></td></tr></table></figure>
<p>ESlint 还有很多功能和检查规则，由于篇幅有限这里就不详细介绍，可以去其官网阅读文档。</p>
<h3 id="检查-TypeScript"><a href="#检查-TypeScript" class="headerlink" title="检查 TypeScript"></a>检查 TypeScript</h3><p>TSLint 是一个和 ESlint 相似的 TypeScript 代码检查工具，区别在于 TSLint 只专注于检查 TypeScript 代码。</p>
<p>TSLint 和 ESlint 的使用方法很相似，首先通过</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -g tslint</span><br></pre></td></tr></table></figure>
<p>按照到全局，再去项目根目录下执行</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tslint --init</span><br></pre></td></tr></table></figure>
<p>生成配置文件 tslint.json，在配置好后，再执行</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tslint yourfile.ts</span><br></pre></td></tr></table></figure>
<p>去检查 yourfile.ts 文件。</p>
<h3 id="检查-CSS"><a href="#检查-CSS" class="headerlink" title="检查 CSS"></a>检查 CSS</h3><p>stylelint 是目前最成熟的 CSS 检查工具，内置了大量检查规则的同时也提供插件机制让用户自定义扩展。 stylelint 基于 PostCSS，能检查任何 PostCSS 能解析的代码，诸如 SCSS、Less 等。</p>
<p>首先通过</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -g stylelint</span><br></pre></td></tr></table></figure>
<p>按照到全局后，去项目根目录下新建 .stylelintrc 配置文件， 该配置文件格式为 JSON，其格式和 ESLint 的配置相似，例如：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="comment">// 继承 stylelint-config-standard 中的所有检查规则</span></span><br><span class="line">  <span class="string">"extends"</span>: <span class="string">"stylelint-config-standard"</span>,</span><br><span class="line">  <span class="comment">// 再自定义检查规则  </span></span><br><span class="line">  <span class="string">"rules"</span>: &#123;</span><br><span class="line">    <span class="string">"at-rule-empty-line-before"</span>: <span class="literal">null</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>配置好后，再执行</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">stylelint <span class="string">"yourfile.css"</span></span><br></pre></td></tr></table></figure>
<p>去检查 yourfile.css 文件。</p>
<p>stylelint 还有很多功能和配置项没有介绍到，可以访问其官方进一步了解。</p>
<p>目前很多编辑器，例如 Webstorm、VSCode 等，已经集成了以上介绍过的检查工具，编辑器会实时地把检查工具输出的错误显示编辑的源码上。 通过编辑器集成后，你不用通过命令行的方式去定位错误。</p>
<h2 id="结合-Webpack-检查代码"><a href="#结合-Webpack-检查代码" class="headerlink" title="结合 Webpack 检查代码"></a>结合 Webpack 检查代码</h2><p>以上介绍的代码检查工具可以和 Webpack 结合起来，在开发过程中通过 Webpack 输出实时的检查结果。</p>
<h3 id="结合-ESLint"><a href="#结合-ESLint" class="headerlink" title="结合 ESLint"></a>结合 ESLint</h3><p>eslint-loader 可以方便的把 ESLint 整合到 Webpack 中，使用方法如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.js$/</span>,</span><br><span class="line">        <span class="comment">// node_modules 目录的下的代码不用检查</span></span><br><span class="line">        exclude: <span class="regexp">/node_modules/</span>,</span><br><span class="line">        loader: <span class="string">'eslint-loader'</span>,</span><br><span class="line">        <span class="comment">// 把 eslint-loader 的执行顺序放到最前面，防止其它 Loader 把处理后的代码交给 eslint-loader 去检查</span></span><br><span class="line">        enforce: <span class="string">'pre'</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>接入 eslint-loader 后就能在控制台中看到 ESLint 输出的错误日志了。</p>
<h3 id="结合-TSLint"><a href="#结合-TSLint" class="headerlink" title="结合 TSLint"></a>结合 TSLint</h3><p>tslint-loader 是一个和 eslint-loader 相似的 Webpack Loader， 能方便的把 TSLint 整合到 Webpack，其使用方法如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.ts$/</span>,</span><br><span class="line">        <span class="comment">// node_modules 目录的下的代码不用检查</span></span><br><span class="line">        exclude: <span class="regexp">/node_modules/</span>,</span><br><span class="line">        loader: <span class="string">'tslint-loader'</span>,</span><br><span class="line">        <span class="comment">// 把 tslint-loader 的执行顺序放到最前面，防止其它 Loader 把处理后的代码交给 tslint-loader 去检查</span></span><br><span class="line">        enforce: <span class="string">'pre'</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="结合-stylelint"><a href="#结合-stylelint" class="headerlink" title="结合 stylelint"></a>结合 stylelint</h3><p>StyleLintPlugin 能把 stylelint 整合到 Webpack，其使用方法很简单，如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> StyleLintPlugin = <span class="built_in">require</span>(<span class="string">'stylelint-webpack-plugin'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  plugins: [</span><br><span class="line">    <span class="keyword">new</span> StyleLintPlugin(),</span><br><span class="line">  ],</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>一些建议<br>把代码检查功能整合到 Webpack 中会导致以下问题：</p>
<ul>
<li>由于执行检查步骤计算量大，整合到 Webpack 中会导致构建变慢；</li>
<li>在整合代码检查到 Webpack 后，输出的错误信息是通过行号来定位错误的，没有编辑器集成显示错误直观；</li>
</ul>
<p>为了避免以上问题，还可以这样做：</p>
<ul>
<li>使用集成了代码检查功能的编辑器，让编辑器实时直观地显示错误；</li>
<li>把代码检查步骤放到代码提交时，也就是说在代码提交前去调用以上检查工具去检查代码，只有在检查都通过时才提交代码，这样就能保证提交到仓库的代码都是通过了检查的。</li>
<li>如果你的项目是使用 Git 管理，Git 提供了 Hook 功能能做到在提交代码前触发执行脚本。</li>
</ul>
<p>husky 可以方便快速地为项目接入 Git Hook， 执行</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -D husky</span><br></pre></td></tr></table></figure>
<p>安装 husky 时，husky 会通过 Npm Script Hook 自动配置好 Git Hook，你需要做的只是在 package.json 文件中定义几个脚本，方法如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="string">"scripts"</span>: &#123;</span><br><span class="line">    <span class="comment">// 在执行 git commit 前会执行的脚本  </span></span><br><span class="line">    <span class="string">"precommit"</span>: <span class="string">"npm run lint"</span>,</span><br><span class="line">    <span class="comment">// 在执行 git push 前会执行的脚本  </span></span><br><span class="line">    <span class="string">"prepush"</span>: <span class="string">"lint"</span>,</span><br><span class="line">    <span class="comment">// 调用 eslint、stylelint 等工具检查代码</span></span><br><span class="line">    <span class="string">"lint"</span>: <span class="string">"eslint &amp;&amp; stylelint"</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>precommit 和 prepush 你需要根据自己的情况选择一个，无需两个都设置。</p>
<h1 id="加载图片和其他静态资源"><a href="#加载图片和其他静态资源" class="headerlink" title="加载图片和其他静态资源"></a>加载图片和其他静态资源</h1><p>在网页中不可避免的会依赖图片资源，例如 PNG、JPG、GIF，下面来教你如何用 Webpack 加载图片资源。</p>
<h2 id="使用-file-loader"><a href="#使用-file-loader" class="headerlink" title="使用 file-loader"></a>使用 file-loader</h2><p>file-loader 可以把 JavaScript 和 CSS 中导入图片的语句替换成正确的地址，并同时把文件输出到对应的位置。</p>
<p>例如 CSS 源码是这样写的：</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-id">#app</span> &#123;</span><br><span class="line">  <span class="attribute">background-image</span>: <span class="built_in">url</span>(./imgs/a.png);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>被 file-loader 转换后输出的 CSS 会变成这样：</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-id">#app</span> &#123;</span><br><span class="line">  <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="number">5556</span>e1251a78c5afda9ee7dd06ad109b.png);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>并且在输出目录 dist 中也多出 ./imgs/a.png 对应的图片文件 5556e1251a78c5afda9ee7dd06ad109b.png， 输出的文件名是根据文件内容的计算出的 Hash 值。</p>
<p>同理在 JavaScript 中导入图片的源码如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> imgB <span class="keyword">from</span> <span class="string">'./imgs/b.png'</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">window</span>.document.getElementById(<span class="string">'app'</span>).innerHTML = <span class="string">`</span></span><br><span class="line"><span class="string">&lt;img src="<span class="subst">$&#123;imgB&#125;</span>"/&gt;</span></span><br></pre></td></tr></table></figure>
<p>经过 file-loader 处理后输出的 JavaScript 代码如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = __webpack_require__.p + <span class="string">"0bcc1f8d385f78e1271ebfca50668429.png"</span>;</span><br></pre></td></tr></table></figure>
<p>也就是说 imgB 的值就是图片对应的 URL 地址。</p>
<p>在 Webpack 中使用 file-loader 非常简单，相关配置如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.png$/</span>,</span><br><span class="line">        use: [<span class="string">'file-loader'</span>]</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="使用-url-loader"><a href="#使用-url-loader" class="headerlink" title="使用 url-loader"></a>使用 url-loader</h2><p>url-loader 可以把文件的内容经过 <strong>base64 编码</strong>后注入到 JavaScript 或者 CSS 中去。</p>
<p>例如 CSS 源码是这样写的：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">#app &#123;</span><br><span class="line">  background-image: url(./imgs/a.png);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>被 url-loader 转换后输出的 CSS 会变成这样：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">#app &#123;</span><br><span class="line">  background-image: url(...); <span class="comment">/* 结尾省略了剩下的 base64 编码后的数据 */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>同理在 JavaScript 中效果也类似。</p>
<p>从上面的例子中可以看出 url-loader 会把根据图片内容计算出的 base64 编码的字符串直接注入到代码中，由于一般的图片数据量巨大， 这会导致 JavaScript、CSS 文件也跟着变大。 所以在使用 <strong>url-loader 时一定要注意图片体积不能太大</strong>，不然会导致 JavaScript、CSS 文件过大而带来的网页加载缓慢问题。</p>
<p>一般利用 url-loader 把网页需要用到的小图片资源注入到代码中去，以减少加载次数。因为在 HTTP/1 协议中，每加载一个资源都需要建立一次 HTTP 链接， 为了一个很小的图片而新建一次 HTTP 连接是不划算的。</p>
<p>url-loader 考虑到了以上问题，并提供了一个方便的选择 limit，该选项用于控制当文件大小小于 limit 时才使用 url-loader，否则使用 fallback 选项中配置的 loader。 相关 Webpack 配置如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.png$/</span>,</span><br><span class="line">        use: [&#123;</span><br><span class="line">          loader: <span class="string">'url-loader'</span>,</span><br><span class="line">          options: &#123;</span><br><span class="line">            <span class="comment">// 30KB 以下的文件采用 url-loader</span></span><br><span class="line">            limit: <span class="number">1024</span> * <span class="number">30</span>,</span><br><span class="line">            <span class="comment">// 否则采用 file-loader，默认值就是 file-loader </span></span><br><span class="line">            fallback: <span class="string">'file-loader'</span>,</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;]</span><br><span class="line">      &#125;</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="webpack打包后的图片无法显示问题"><a href="#webpack打包后的图片无法显示问题" class="headerlink" title="webpack打包后的图片无法显示问题"></a>webpack打包后的图片无法显示问题</h2><p>之前做react项目的时候遇到过这种问题，项目打包后页面图片加载有问题，打开控制台看到类似这样的错误<br><img src="https://img-blog.csdnimg.cn/20200730185528802.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTIwMzE5NTg=,size_16,color_FFFFFF,t_70" alt=""><br>webpack要将图片进行打包，需要安装儒url-loader加载器，加载器有个默认的设置选项limit：8196，当你的图片大小不超过8kb的时候，打包的时候会生成base64位的图片地址，这种情况下背景图片可以正常显示，当你图片大小超过limit设置的限制时，它会生成一个静态资源图片</p>
<p>打包后的图片一般会存放在dist文件夹下，但是由于某种原因（暂时不知道什么原因），打包后的代码引用的资源地址依然是没有打包之前的那个地址，那自然就会报错。</p>
<p>解决方案1：将limit这个配置变大或者注释掉，都以base64位的地址显示图片<br>解决方案2：<br>在webpack.config.js中添加配置：<br><img src="https://img-blog.csdnimg.cn/20200730185913496.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTIwMzE5NTg=,size_16,color_FFFFFF,t_70" alt=""></p>
<h1 id="为单页应用生成HTML"><a href="#为单页应用生成HTML" class="headerlink" title="为单页应用生成HTML"></a>为单页应用生成HTML</h1><p>截止到目前，我们的hello，webpack例子在构建时会输出一个bundle.js文件，而要想访问页面必须首先更改html文件当中script文件的引入路径，然后打开这个html文件</p>
<p>不仅如此，实际的项目其实远比我们这个例子复杂得多，例如：</p>
<ul>
<li>项目采用 ES6 语言加 React 框架。</li>
<li>给页面加入 Google Analytics，这部分代码需要内嵌进 HEAD 标签里去。</li>
<li>给页面加入 Disqus 用户评论，这部分代码需要异步加载以提升首屏加载速度。</li>
<li>压缩和分离 JavaScript 和 CSS 代码，提升加载速度。</li>
</ul>
<p>在开始前先来看看该应用最终发布到线上的代码：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!--注入 Chunk app 依赖的 CSS--&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">style</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span>&gt;</span><span class="css"><span class="selector-tag">h1</span>&#123;<span class="attribute">color</span>:red&#125;</span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!--内嵌 google_analytics 中的 JavaScript 代码--&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="actionscript">(<span class="function"><span class="keyword">function</span><span class="params">(i,s,o,g,r,a,m)</span></span>&#123;i[<span class="string">'GoogleAnalyticsObject'</span>]=r;i[r]=i[r]||<span class="function"><span class="keyword">function</span><span class="params">()</span></span>&#123;</span></span><br><span class="line"><span class="javascript">(i[r].q=i[r].q||[]).push(<span class="built_in">arguments</span>)&#125;,i[r].l=<span class="number">1</span>*<span class="keyword">new</span> <span class="built_in">Date</span>();a=s.createElement(o),</span></span><br><span class="line">m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)</span><br><span class="line"><span class="javascript">&#125;)(<span class="built_in">window</span>,<span class="built_in">document</span>,<span class="string">'script'</span>,<span class="string">'https://www.google-analytics.com/analytics.js'</span>,<span class="string">'ga'</span>);</span></span><br><span class="line"><span class="actionscript">ga(<span class="string">'create'</span>, <span class="string">'UA-XXXXX-Y'</span>, <span class="string">'auto'</span>);</span></span><br><span class="line"><span class="actionscript">ga(<span class="string">'send'</span>, <span class="string">'pageview'</span>);</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!--异步加载 Disqus 评论--&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">async</span>=<span class="string">""</span> <span class="attr">src</span>=<span class="string">"https://dive-into-webpack.disqus.com/embed.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"app"</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!--导入 app 依赖的 JS--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"app_746f32b2.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!--Disqus 评论容器--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"disqus_thread"</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>HTML 应该是被压缩过的，这里为了方便大家阅读而格式化了 HTML，并且加入了注释。</p>
<p>构建出的目录结构为：</p>
<p>dist<br>├── app_792b446e.js<br>└── index.html</p>
<p>可以看到部分代码被内嵌进了 HTML 的 HEAD 标签中，部分文件的文件名称被打上根据文件内容算出的 Hash 值，并且加载这些文件的 URL 地址也被正常的注入到了 HTML 中。 如果你还采用手写 index.html 文件去完成以上要求，这就会使工作变得复杂、易错，项目难以维护。 本节教你如何自动化的生成这个符合要求的 index.html。</p>
<h2 id="解决方案-1"><a href="#解决方案-1" class="headerlink" title="解决方案"></a>解决方案</h2><p>推荐一个用于方便的解决以上问题的 Webpack 插件 web-webpack-plugin。 该插件已经被社区上许多人使用和验证，解决了大家的痛点获得了很多好评，下面具体介绍如何用它来解决上面的问题。</p>
<p>首先，修改 Webpack 配置为如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"><span class="keyword">const</span> UglifyJsPlugin = <span class="built_in">require</span>(<span class="string">'webpack/lib/optimize/UglifyJsPlugin'</span>);</span><br><span class="line"><span class="keyword">const</span> ExtractTextPlugin = <span class="built_in">require</span>(<span class="string">'extract-text-webpack-plugin'</span>);</span><br><span class="line"><span class="keyword">const</span> DefinePlugin = <span class="built_in">require</span>(<span class="string">'webpack/lib/DefinePlugin'</span>);</span><br><span class="line"><span class="keyword">const</span> &#123; WebPlugin &#125; = <span class="built_in">require</span>(<span class="string">'web-webpack-plugin'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="comment">// 此时的entry必须写成这样的对象形式</span></span><br><span class="line">  entry: &#123;</span><br><span class="line">    main: <span class="string">'./main.js'</span><span class="comment">// app 的 JavaScript 执行入口文件</span></span><br><span class="line">  &#125;,</span><br><span class="line">  output: &#123;</span><br><span class="line">    filename: <span class="string">'[name]_[chunkhash:8].js'</span>,<span class="comment">// 给输出的文件名称加上 Hash 值</span></span><br><span class="line">    path: path.resolve(__dirname, <span class="string">'./dist'</span>),</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="built_in">module</span>: &#123;</span><br><span class="line">    rules: [</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.js$/</span>,</span><br><span class="line">        use: [<span class="string">'babel-loader'</span>],</span><br><span class="line">        <span class="comment">// 排除 node_modules 目录下的文件，</span></span><br><span class="line">        <span class="comment">// 该目录下的文件都是采用的 ES5 语法，没必要再通过 Babel 去转换</span></span><br><span class="line">        exclude: path.resolve(__dirname, <span class="string">'node_modules'</span>),</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        test: <span class="regexp">/\.css$/</span>,<span class="comment">// 增加对 CSS 文件的支持</span></span><br><span class="line">        <span class="comment">// 提取出 Chunk 中的 CSS 代码到单独的文件中</span></span><br><span class="line">        use: ExtractTextPlugin.extract(&#123;</span><br><span class="line">          use: [<span class="string">'css-loader?minimize'</span>] <span class="comment">// 压缩 CSS 代码</span></span><br><span class="line">        &#125;),</span><br><span class="line">      &#125;,</span><br><span class="line">    ]</span><br><span class="line">  &#125;,</span><br><span class="line">  plugins: [</span><br><span class="line">    <span class="comment">// 使用本文的主角 WebPlugin，一个 WebPlugin 对应一个 HTML 文件</span></span><br><span class="line">    <span class="keyword">new</span> WebPlugin(&#123;</span><br><span class="line">      template: <span class="string">'./template.html'</span>, <span class="comment">// HTML 模版文件所在的文件路径</span></span><br><span class="line">      filename: <span class="string">'index.html'</span> <span class="comment">// 输出的 HTML 的文件名称</span></span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="keyword">new</span> ExtractTextPlugin(&#123;</span><br><span class="line">      filename: <span class="string">`[name]_[contenthash:8].css`</span>,<span class="comment">// 给输出的 CSS 文件名称加上 Hash 值</span></span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="keyword">new</span> DefinePlugin(&#123;</span><br><span class="line">      <span class="comment">// 定义 NODE_ENV 环境变量为 production，以去除源码中只有开发时才需要的部分</span></span><br><span class="line">      <span class="string">'process.env'</span>: &#123;</span><br><span class="line">        NODE_ENV: <span class="built_in">JSON</span>.stringify(<span class="string">'production'</span>)</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="comment">// 压缩输出的 JavaScript 代码</span></span><br><span class="line">    <span class="keyword">new</span> UglifyJsPlugin(&#123;</span><br><span class="line">      <span class="comment">// 最紧凑的输出</span></span><br><span class="line">      beautify: <span class="literal">false</span>,</span><br><span class="line">      <span class="comment">// 删除所有的注释</span></span><br><span class="line">      comments: <span class="literal">false</span>,</span><br><span class="line">      compress: &#123;</span><br><span class="line">        <span class="comment">// 在UglifyJs删除没有用到的代码时不输出警告</span></span><br><span class="line">        warnings: <span class="literal">false</span>,</span><br><span class="line">        <span class="comment">// 删除所有的 `console` 语句，可以兼容ie浏览器</span></span><br><span class="line">        drop_console: <span class="literal">true</span>,</span><br><span class="line">        <span class="comment">// 内嵌定义了但是只用到一次的变量</span></span><br><span class="line">        collapse_vars: <span class="literal">true</span>,</span><br><span class="line">        <span class="comment">// 提取出出现多次但是没有定义成变量去引用的静态值</span></span><br><span class="line">        reduce_vars: <span class="literal">true</span>,</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;),</span><br><span class="line">  ],</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>以上配置中，大多数都是按照前面已经讲过的内容增加的配置，例如：</p>
<ul>
<li>增加对 CSS 文件的支持，提取出 Chunk 中的 CSS 代码到单独的文件中，压缩 CSS 文件；</li>
<li>定义 NODE_ENV 环境变量为 production，以去除源码中只有开发时才需要的部分；</li>
<li>给输出的文件名称加上 Hash 值；</li>
<li>压缩输出的 JavaScript 代码。</li>
</ul>
<p>但最核心的部分在于 plugins 里的：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> WebPlugin(&#123;</span><br><span class="line">  template: <span class="string">'./template.html'</span>, <span class="comment">// HTML 模版文件所在的文件路径</span></span><br><span class="line">  filename: <span class="string">'index.html'</span> <span class="comment">// 输出的 HTML 的文件名称</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<p>其中 template: ‘./template.html’ 所指的模版文件 template.html 的内容是：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"UTF-8"</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!--注入 Chunk app 中的 CSS--&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"stylesheet"</span> <span class="attr">href</span>=<span class="string">"main?_inline"</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!--注入 google_analytics 中的 JavaScript 代码--&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"./google_analytics.js?_inline"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="comment">&lt;!--异步加载 Disqus 评论--&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"https://dive-into-webpack.disqus.com/embed.js"</span> <span class="attr">async</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"app"</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!--导入 Chunk app 中的 JS--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"main"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="comment">&lt;!--Disqus 评论容器--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">"disqus_thread"</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>该文件描述了哪些资源需要被以何种方式加入到输出的 HTML 文件中。</p>
<p>以 <code>&lt;link rel=&quot;stylesheet&quot; href=&quot;app?_inline&quot;&gt;</code> 为例，按照正常引入 CSS 文件一样的语法来引入 Webpack 生产的代码。 href 属性中的 app?_inline 可以分为两部分，前面的 app 表示 CSS 代码来自名叫 app 的 Chunk 中，后面的 _inline 表示这些代码需要被内嵌到这个标签所在的位置。</p>
<p>同样的 <code>&lt;script src=&quot;./google_analytics.js?_inline&quot;&gt;&lt;/script&gt;</code> 表示 JavaScript 代码来自相对于当前模版文件 template.html 的本地文件 ./google_analytics.js， 而且文件中的 JavaScript 代码也需要被内嵌到这个标签所在的位置。</p>
<p>也就是说资源链接 URL 字符串里问号前面的部分表示资源内容来自哪里，后面的 querystring 表示这些资源注入的方式。</p>
<p>除了 _inline 表示内嵌外，还支持以下属性：</p>
<ul>
<li>_dist 只有在生产环境下才引入该资源</li>
<li>_dev 只有在开发环境下才引入该资源</li>
<li>_ie 只有IE浏览器才需要引入的资源，通过 [if IE]&gt;resource&lt;![endif] 注释实现<br>这些属性之间可以搭配使用，互不冲突。例如 app?_inline&amp;_dist 表示只在生产环境下才引入该资源，并且需要内嵌到 HTML 里去。</li>
</ul>
<p>WebPlugin 插件还支持一些其它更高级的用法，详情可以访问该项目主页阅读文档。</p>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>Copyright： </strong>
          Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2020/08/07/JQ-CSS%E5%AE%9E%E7%8E%B0%E8%80%81%E8%99%8E%E6%9C%BA%E6%8A%BD%E5%A5%96%E6%95%88%E6%9E%9C/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            JQ-CSS实现老虎机抽奖效果
          
        </div>
      </a>
    
    
      <a href="/2020/08/03/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA-Webpack%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E5%9B%9B%EF%BC%89%E2%80%94%E2%80%94%E4%BC%98%E5%8C%96%C2%B7%E4%B8%8A/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">深入浅出-Webpack学习笔记（四）——优化·上</div>
      </a>
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "2eBxdPB3eEwraN7IpwFckzeL-gzGzoHsz",
    app_key: "VGgCefQEluVxHdyMsm1Dpeih",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "评论一下我的文章吧~",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2015-2020
        <i class="ri-heart-fill heart_icon"></i> AURORA_ZXH
      </li>
    </ul>
    <ul>
      <li>
        
        
        
        Powered by <a href="https://hexo.io" target="_blank">Hexo</a>
        <span class="division">|</span>
        Theme - <a href="https://github.com/Shen-Yu/hexo-theme-ayer" target="_blank">Ayer</a>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>Visitors:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>Views:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/avatar.jpg" alt="AURORA"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="http://aurora-zxh.lofter.com/" target="_blank" rel="noopener">随记</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/about">关于我</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/link">友链</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="Search">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="/images/alipay.png">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="/images/wxpay.png">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->


<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: 'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto'
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


    
  </div>
</body>

</html>